/**
 *Stack holds all the figures on the screen
 *It will also hold the groups formed on the screen
 *@this {Stack}
 *@constructor
 **/
function Stack(){
    /**Keeps all the figures on the canvas*/
    this.figures = []; 
    
    /**Keeps all the groups in the canvas*/
    this.groups = [];
    
    this.containers = [];
    
    /**Keeps current generated Id. Not for direct access*/
    this.currentId = 0;
    
    /**Keeps a map like (figure Id, figure index). It is similar to an index*/
    this.idToIndex = [];
    
    /**Type used in serialization*/
    this.oType = 'Stack';
}



/**Creates a {Stack} out of JSON parsed object
 *@param {JSONObject} o - the JSON parsed object
 *@return {Stack} a newly constructed Stack
 *@author Alex Gheorghiu <alex@scriptoid.com>
 **/
Stack.load = function(o){
    var newStack = new Stack(); //empty constructor
    

    newStack.figures = Figure.loadArray(o.figures);
    newStack.containers = Container.loadArray(o.containers);
    newStack.groups = Group.loadArray(o.groups);
    newStack.figureSelectedIndex = o.figureSelectedIndex;
    newStack.currentId = o.currentId;
    newStack.idToIndex = o.idToIndex;

    return newStack;
}


Stack.prototype = {

    constructor : Stack,
    
    /**Creates a {Group} based on a set of figure IDs
     * Group is created by 1. creating a Group and 2. setting Figure's groupId property to the new id of the Group
     *@param {Array} figureIds - all the ids of {Figure}s
     *@param {Number} groupId - the id of the {Group} (optional) 
     *@return {Number} - the id of newly created Group
     **/
    groupCreate:function (figureIds, groupId){

        //we should allow to create more than one temporary group
        for(var i=0; i<this.groups.length; i++){
            if(this.groups[i].permanent == false){
                throw 'Stack::groupCreate()  You can not create new groups while you have temporary groups alive';
            }
        }
        
        //create group
        var g = new Group(groupId);

        //add figures to group
        for(var i=0; i < figureIds.length; i++){
            var f = this.figureGetById(figureIds[i]);
            f.groupId = g.id;
        }
        var bounds = g.getBounds();
        g.rotationCoords.push(new Point(bounds[0]+(bounds[2]-bounds[0])/2, bounds[1] + (bounds[3] - bounds[1]) / 2));
        g.rotationCoords.push(new Point(bounds[0]+(bounds[2]-bounds[0])/2, bounds[1]));

        //save group to STACK
        this.groups.push(g);

        return g.id;
    },


    /**Finds a {Group} by it's id
     *@param {Number} groupId - the {Group}'s id
     *@return {Group} founded group of null if none finded
     **/
    groupGetById : function(groupId){
        for(var i=0; i<this.groups.length; i++){
            if(this.groups[i].id == groupId){
                return this.groups[i];
            }
        }
        return null;
    },

    /**Destroy a group by it's Id. 
     *I will be removed from Stack's groups and any member figure will be removed
     *from group (by set the groupId to -1)
     * @param {Number} groupId - the id of the group
     **/
    groupDestroy: function(groupId){
        var index = -1;
        
        //search for the group
        for(var i=0; i<this.groups.length; i++ ){
            if(this.groups[i].id == groupId){
                index = i;
                break;
            }
        }

        //remove it
        if(index > -1){
            //remove Group
            this.groups.splice(index, 1);

            //remove the Figures from Group
            var groupFigures = this.figureGetByGroupId(groupId);
            for(var i=0; i<groupFigures.length; i++ ){
                groupFigures[i].groupId = -1;
            }
        } 
    },
    
    /**Removes any temporary group*/
    groupRemoveTemporary : function(){
        throw Exception("Not implemented");
    },
    
    /**See if this STACK is equal to another. It is a shallow compare.
     *@param {Stack} anotherStack - the other STACK object
     *@return {Boolean} - true if equals, false otherwise
     **/
    equals: function(anotherStack){
        var msg = '';
        if(!anotherStack instanceof Stack){
            return false;
            msg += 'not same class';
        }

        
        
        //test figures
        if(this.figures.length != anotherStack.figures.length){
            msg += 'not same nr of figures';
            return false;
        }
        
        
        for(var i =0; i<this.figures.length; i++){
            if( !this.figures[i].equals(anotherStack.figures[i]) ){
                msg += 'figures not the same';
                return false;                
            }
        }

        
        
        //test groups
        if(this.groups.length != anotherStack.groups.length){
            msg += 'not same nr of groups';
            return false;
        }

        for(var i =0; i<this.groups.length; i++){
            if( !this.groups[i].equals(anotherStack.groups[i]) ){
                msg += 'groups not the same';
                return false;
            }
        }



        //test idToIndex
        for(var i =0; i<this.figures.idToIndex; i++){
            if(this.idToIndex[i] != undefined //if not undefined
                && anotherStack.idToIndex != undefined //if not undefined
                && this.idToIndex[i] != anotherStack.idToIndex[i] )
                {

                msg += 'not same idToIndex';
                return false;
                }
        }

        if(this.currentId != anotherStack.currentId){
            msg += 'not same currentId';
            return false;
        }

        if(msg != ''){
            alert(msg);
        }

        return true;
            
    },

    /**Generates an returns a new unique ID
     *@return {Number} - next id*/
    generateId:function(){
        return this.currentId++;
    },
    
    /**Adds a figure to the Stack of figures
     *@param {Figure} figure - the figure to add
     **/
    figureAdd:function(figure){
        this.figures.push(figure);
        this.idToIndex[figure.id] = this.figures.length-1;
    },
    
    
    containerAdd : function(container){
        this.containers.push(container);
    },

    /*code taken from ConnectionPoint.removeConnector
     *@param {Figure} figure - the figure to remove
     *@deprecated
     **/
    figureRemove_deprecated:function(figure){
        var index = -1;
        for(var i=0; i<this.figures.length; i++ ){
            if(this.figures[i] == figure){
                index = i;
                break;
            }
        }
        CONNECTOR_MANAGER.connectionPointRemoveAllByParent(figure.id);
        if(index > -1){
            this.figures.splice(index, 1);
            for(var i=index; i<this.figures.length; i++){
                this.idToIndex[this.figures[i].id]=i;
            }
        }
        if(index<this.figureSelectedIndex && index!=-1){
            this.figureSelectedIndex--;
        }
    },
            
    /**Removes a container by it's id
     *@param {Number} containerId - the {Container}'s id
     *@author Alex Gheorghiu <alex@scriptoid.com>
     **/
    containerRemoveById :function(containerId){
        var index = -1;
        for(var i=0; i<this.containers.length; i++ ){
            if(this.containers[i].id === containerId){
                index = i;
                break;
            }
        }

        if(index > -1){
            //remove figure
            this.containers.splice(index, 1);

            //reindex
//            this.reindex();
        }                
    },            

    /**Removes a figure by it's id
     *@param {Number} figId - the {Figure}'s id
     *@author Alex Gheorghiu <alex@scriptoid.com>
     **/
    figureRemoveById :function(figId){
        var index = -1;
        for(var i=0; i<this.figures.length; i++ ){
            if(this.figures[i].id == figId){
                index = i;
                break;
            }
        }

        if(index > -1){
            //remove all affected Glues
            var cCPs = CONNECTOR_MANAGER.connectionPointGetAllByParent(figId); //get all connection points
            var length = cCPs.length;
            var k;
            for(k = 0; k < length; k++) {
                CONNECTOR_MANAGER.glueRemoveAllByFirstId(cCPs[k].id);
            }

            // remove figure's connection points
            CONNECTOR_MANAGER.connectionPointRemoveAllByParent(figId);

            //remove figure
            this.figures.splice(index, 1);

            //reindex
            this.reindex();
        }                
    },


    /**Recreates the index (id, index)
     *@author Alex Gheorghiu <alex@scriptoid.com>
     **/
    reindex : function(){
        for(var i=0; i<this.figures.length; i++){
            this.idToIndex[this.figures[i].id] = i;
        }
    },

    /**Deletes all the figure and reset any index*/
    reset:function(){
        this.figures = [];
        this.figureSelectedIndex = -1;
        this.currentId = 0;
    },

    /**Find the storage index of a figure
     *@param {Figure} figure - the figure you search for
     *@return {Number} - the index where you can find the Figure or -1 if not founded
     **/
    getIndex:function(figure){
        for(var i=0; i<this.figures.length; i++){
            if(this.figures[i]==figure){
                return i;
            }
        }
        return -1;
    },



    /**Returns all figures from a group
     *@param {Number} groupId - the id of the group
     *@return {Array} - the {Array} of {Figure}s that belong to the group
     **/
    figureGetByGroupId:function(groupId){
        var groupFigures = [];
        for(var i=0; i<this.figures.length; i++){
            if(this.figures[i].groupId == groupId){
                groupFigures.push(this.figures[i]);
            }
        }
        
        return groupFigures;
    },


    /**Returns all figures ids from a group
     *@param {Number} groupId - the id of the group
     *@return {Array} - the {Array} of {Number}s that belong to the group
     **/
    figureGetIdsByGroupId:function(groupId){
        var groupFiguresIds = [];
        for(var i=0; i<this.figures.length; i++){
            if(this.figures[i].groupId == groupId){
                groupFiguresIds.push(this.figures[i].id);
            }
        }

        return groupFiguresIds;
    },

    /**Returns a figure by id
     *@param {Number} id - the id of the figure
     *@return {Figure} - the figure object or null if no figure with that id found
     *TODO: use idToIndex to speed up the search....well there is no search at all :)
     **/
    figureGetById:function(id){
        for(var i=0; i<this.figures.length; i++){
            if(this.figures[i].id == id){
                return this.figures[i];
            }
        }
        return null;
    },
    
    
    /**
     *Returns first figure glued to a connector
     *@param {Number} connectorId - the id of the connector
     *@return {Figure} - the figure connected, or null if none 
     **/
    figureGetAsFirstFigureForConnector: function(connectorId){
        Log.group("Stack:figureGetAsFirstFigureForConnector");
        
        /*Algorithm
         *Connector -> first Connector's ConnectionPoint-> Glue -> Figure's ConnectionPoint -> Figure
         **/
        var figure = null;
        
        //var connector = CONNECTOR_MANAGER.connectorGetById(connectorId);        
        Log.debug("Connector id = " + connectorId);
        
        var startConnectionPoint = CONNECTOR_MANAGER.connectionPointGetFirstForConnector(connectorId);
        Log.debug("ConnectionPoint id = " + startConnectionPoint.id);
        
        var startGlue = CONNECTOR_MANAGER.glueGetBySecondConnectionPointId(startConnectionPoint.id)[0];
        if(startGlue){
            Log.debug("Glue id1 = (" + startGlue.id1 + ", " + startGlue.id2 + ')');

            var figureConnectionPoint = CONNECTOR_MANAGER.connectionPointGetById(startGlue.id1);
            Log.debug("Figure's ConnectionPoint id = " + figureConnectionPoint.id);

            figure = this.figureGetById(figureConnectionPoint.parentId);
        }
        else{
            Log.debug("no glue");
        }
                                
        Log.groupEnd();
        
        return figure;
    },
    
    
    /**
     *Returns second figure glued to a connector
     *@param {Number} connectorId - the id of the connector
     *@return {Figure} - the figure connected, or null if none 
     **/
    figureGetAsSecondFigureForConnector: function(connectorId){
        Log.group("Stack:figureGetAsSecondFigureForConnector");
        
        /*Algorithm
         *Connector -> first Connector's ConnectionPoint-> Glue -> Figure's ConnectionPoint -> Figure
         **/
        var figure = null;
        
        //var connector = CONNECTOR_MANAGER.connectorGetById(connectorId);        
        Log.debug("Connector id = " + connectorId);
        
        var endConnectionPoint = CONNECTOR_MANAGER.connectionPointGetSecondForConnector(connectorId);
        Log.debug("ConnectionPoint id = " + endConnectionPoint.id);
        
        var startGlue = CONNECTOR_MANAGER.glueGetBySecondConnectionPointId(endConnectionPoint.id)[0];
        if(startGlue){
            Log.debug("Glue id1 = (" + startGlue.id1 + ", " + startGlue.id2 + ')');

            var figureConnectionPoint = CONNECTOR_MANAGER.connectionPointGetById(startGlue.id1);
            Log.debug("Figure's ConnectionPoint id = " + figureConnectionPoint.id);

            figure = this.figureGetById(figureConnectionPoint.parentId);
        }
        else{
            Log.debug("no glue");
        }
                                
        Log.groupEnd();
        
        return figure;
    },



    /**
     *Returns the Figure's id if there is a figure for the given coordinates
     *It will return the first figure we found from top to bottom (Z-order)
     *@param {Number} x - the value on Ox axis
     *@param {Number} y - the value on Ox axis
     *@return {Number} - the id of the figure or -1 if none found
     *@author Alex Gheorghiu <alex@scriptoid.com>
     **/
    figureGetByXY:function(x,y){
        var id = -1;
        for(var i= this.figures.length-1; i>=0; i--){
            if(this.figures[i].contains(x, y)){
                id = this.figures[i].id;
                break;
            /*
                 *Some old stuff left from a previous version. 
                 *TODO: delete it if no longer needed after the grouping has been (re)done
                 *we always want to get the group of a figure, not the figure itself where possible
                *(this section only does anything when we change the order of some items
                *var figure = this.figures[i];
                *While the figure belongs to a group or the group is part of another group
                *we will go up in the ancestor hierarchy
                * while(figure.groupId >= 0){
                *     figure = this.figureGetById(figure.groupId);
                * }
                return figure;
                */
            } //end if
        }//end for
        return id;
    },

    /**
     *Returns the Text primitive of parent figure for the given coordinates
     *It will return the first Text primitive we found from top to bottom (Z-order)
     *@param {Number} fId - the id of figure - parent of target Text primitive
     *@param {Number} x - the value on Ox axis
     *@param {Number} y - the value on Oy axis
     *@return {Number} - the id value of Text primitive or -1 if none found
     *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
     **/
    textGetByFigureXY:function(fId, x, y){
        var figureLength = this.figures.length;
        for(var i = figureLength - 1; i >= 0; i--){
            var figure = this.figures[i];
            if(figure.id === fId){
                var primitiveLength = figure.primitives.length;
                for (var j = primitiveLength - 1; j >= 0; j--) { //top to bottom
                    var primitive = figure.primitives[j];
                    if( (primitive.oType === "Text") && primitive.contains(x, y) ){
                        return primitive.id;
                    }
                }
            } //end if
        }//end for
        return -1;
    },

    /**
     *Returns the Text primitive of parent container for the given coordinates
     *It will return the first Text primitive we found from top to bottom (Z-order)
     *@param {Number} cId - the id of container - parent of target Text primitive
     *@param {Number} x - the value on Ox axis
     *@param {Number} y - the value on Oy axis
     *@return {Number} - the id value of Text primitive or -1 if none found
     *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
     **/
    textGetByContainerXY:function(cId, x, y){
        var containerLength = this.containers.length;
        for(var i = containerLength - 1; i >= 0; i--){
            var container = this.containers[i];
            if(container.id === cId){
                var primitiveLength = container.primitives.length;
                for (var j = primitiveLength - 1; j >= 0; j--) {
                    var primitive = container.primitives[j];
                    if( (primitive.oType == "Text") && primitive.contains(x, y) ){
                        return primitive.id;
                    }
                }
            } //end if
        }//end for
        return -1;
    },
    
    /**
     *Returns the Container's id if there is a container for the given coordinates
     *It will return the first container we found from top to bottom (Z-order)
     *@param {Number} x - the value on Ox axis
     *@param {Number} y - the value on Ox axis
     *@return {Number} - the id of the container or -1 if none found
     *@author Alex Gheorghiu <alex@scriptoid.com>
     **/
    containerGetByXY:function(x,y){
        var id = -1;
        for(var i= this.containers.length-1; i>=0; i--){
            if(this.containers[i].contains(x, y)){
                id = this.containers[i].id;
                break;
            } //end if
        }//end for
        return id;
    },
    
    
    /**
     *Returns the Container's id if there is a container for the given coordinates
     *It will return the first container we found from top to bottom (Z-order)
     *@param {Number} x - the value on Ox axis
     *@param {Number} y - the value on Ox axis
     *@return {Number} - the id of the {Container} or -1 if none found
     *@author Alex Gheorghiu <alex@scriptoid.com>
     **/
    containerGetByXYOnEdge:function(x,y){
        var id = -1;
        for(var i= this.containers.length-1; i>=0; i--){
            if(this.containers[i].onEdge(x, y)){
                id = this.containers[i].id;
                break;
            } //end if
        }//end for
        
        return id;
    },
    
    
    /**Returns a container by id
     *@param {Number} id - the id of the container
     *@return {Container} - the container object or null if no container with that id found
     **/
    containerGetById:function(id){
        for(var i=0; i<this.containers.length; i++){
            if(this.containers[i].id == id){
                return this.containers[i];
            }
        }
        return null;
    },
    
    /**
     *Returns an Array of Figure's id if there are figures for the given coordinates
     *@param {Number} x - the value on Ox axis
     *@param {Number} y - the value on Ox axis
     *@return {Array} of {Number} - the array of {Figure} ids. Figures are arranged from top (closer to viewer)
     * to bottom (far from viewer)
     *@author Alex Gheorghiu <alex@scriptoid.com>
     **/
    figuresGetByXY:function(x,y){
        var ids = [];
        
        for(var i= this.figures.length-1; i>=0; i--){
            if(this.figures[i].contains(x, y)){
                ids.push(this.figures[i].id);
            } 
        }
        
        return ids;
    },
    /**Returns a imageFrame by figureId
    *@param {Number} figureId - the figureId of the Figure
    *@return {ImageFrame} - the ImageFrame object or null if no ImageFrame with that found
    **/
    figuresImagePrimitiveGetByFigureId: function (figureId) {
        var f = this.figureGetById(figureId);
        var imageFrame = null;
        for (var i = 0; i < f.primitives.length; i++) {
            if (f.primitives[i].oType == 'ImageFrame') {
                imageFrame = f.primitives[i];
            }
        }
        return imageFrame;
    },
    /**Returns a Text by figureId
    *@param {Number} figureId - the figureId of the Figure
    *@return {Text} - the Text object or null if no Text with that found
    **/
    figuresTextPrimitiveGetByFigureId: function (figureId) {
        var f = this.figureGetById(figureId);
        var text = null;
        for (var i = 0; i < f.primitives.length; i++) {
            if (f.primitives[i].oType == 'Text') {
                text = f.primitives[i];
            }
        }
        return text;
    },

    /* Sets the z index of a figure
     * @param {Figure} figure - the figure to move
     * @param {Number} position - the Z index. The bigger value means  closer to user (last painted);
     * @deprecated
     * */
    setPosition_deprecated:function(figure, position){
        var figureIndex=-1;
        for (var i=0; i<this.figures.length; i++){
            if(this.figures[i]==figure){
                figureIndex=i;
            }
        }
        if(figureIndex!=-1 && position>=0 && position<this.figures.length){
            //tempFigures=[];
            //tempFigures.spli
            //if(position<figureIndex){
            var tempFigure=this.figures.splice(figureIndex,1); //the figure to move
            this.figures.splice(position,0,tempFigure[0]);
            //}
            var added=false
            for(var i=0; i<this.figures.length; i++){
                this.idToIndex[this.figures[i].id] = i;
            }
            this.figureSelectedIndex=position;
        //this.figures=tempFigures;
        }
    },
    
    /** Sets the new z position of a currently selected figure (if present)
     * It actually swap figures.
     * <p/>
     * Note: it's just a simple switch between current position and new position
     * <p/>
     * Zack: Is it just a switch? All you are doing is swapping, what if the user didn't want to swap, but shift up
     * using this method, if you have 5 figures, and bring the very back one to the front, the front figure
     * is moved to the very back, surely the correct solution is to move everything back 1, and move the selected
     * figure to the front
     * <p/>
     * Alex: What you are saying is an insert at a certain position which is not what we want with this method.
     * Maybe we should rename it swapToPosition(...) or swapIntoPosition(...)
     * <p/>
     * @param {Number} figureId - the id of the {Figure}
     * @param {Number} newPosition - the new Z index of the figure. The bigger the value, close to user (last painted);
     * @author Alex Gheorghiu <alex@scriptoid.com>

     * */
    swapToPosition:function(figureId, newPosition){
        var oldPosition = this.idToIndex[figureId];
        
        if(oldPosition != -1 /**oldPosition valid*/
            && newPosition >= 0 && newPosition < this.figures.length /**newPosition in vector bounds*/){
            
            //update idToIndex index
            this.idToIndex[figureId] = newPosition;
            this.idToIndex[this.figures[newPosition].id] = oldPosition;
            
            //switch figures
            var temp = this.figures[oldPosition];
            this.figures[oldPosition] = this.figures[newPosition];
            this.figures[newPosition] = temp;
        }
    },

    
    /**
     *Insert a figure into a position and shifts all other figures
     *Used by moveToBack and moveToFront, sets the selected figure to the selected position, and rotates all other figures away
     *@example
     *[0] = 0
     *[1] = 1
     *[2] = 2
     *
     *change to
     *
     *@example
     *[0] = 1
     *[1] = 2
     *[2] = 0
     *
     *@example
     *figureA
     *figureB
     *figureC
     *
     *change to
     *
     *@example
     *figureB
     *figureC
     *figureA
     *@param {Number} figureId - the id of the figure
     *@param {Number} newPosition - the new position of the figure
     *@author Zack Newsham
     */
    setPosition:function(figureId, newPosition){
        //are we moving forward or back?
        var oldPosition = this.idToIndex[figureId];
        var temp = this.figures[oldPosition];
        var direction = -1;//move to back
        if(oldPosition < newPosition){//move to front
            direction = 1;
        }
        Log.info(direction);
        //while i between oldposition and new position, move 1 in given direction
        for(var i = oldPosition; i != newPosition; i+=direction){
            this.figures[i] = this.figures[i + direction];//set the figure
            this.idToIndex[this.figures[i].id] = i;//change the index
        }
        this.figures[newPosition] = temp; //replace the temp
        this.idToIndex[this.figures[newPosition].id] = newPosition;
    },
    
    
    /**Test if an (x,y) is over a figure
     *@param {Number} x - the x coordinates
     *@param {Number} y - the y coordinates
     *@return {Boolean} - true if over a figure, false otherwise
     **/
    figureIsOver:function(x, y){
        var found = false;
        for(var i=0; i< this.figures.length; i++){
            var figure = this.figures[i];
            if(figure.contains(x, y)){
                found = true;
                break;
            }
        }
        return found;
    },
        
        
    /**Test if an (x,y) is over a container
     *@param {Number} x - the x coordinates
     *@param {Number} y - the y coordinates
     *@return {Boolean} - true if over a container, false otherwise
     **/
    containerIsOver:function(x, y){
        var found = false;
        for(var i=0; i< this.containers.length; i++){
            var container = this.containers[i];
            if(container.contains(x, y)){
                found = true;
                break;
            }
        }
        return found;
    },
        
        
    /**Test if an (x,y) is over a Container's edge (rigt on its edge)
     *@param {Number} x - the x coordinates
     *@param {Number} y - the y coordinates
     *@return {Boolean} - true if over a container, false otherwise
     **/
    containerIsOnEdge:function(x, y){
        var found = false;
        for(var i=0; i< this.containers.length; i++){
            var container = this.containers[i];
            if(container.onEdge(x, y)){
                found = true;
                break;
            }
        }
        return found;
    },

    /**Return the bounds for all objects on work area (canvas).
     *
     *@return {Array<Number>} - returns [minX, minY, maxX, maxY] - bounds, where
     *  all objects on canvas (Figures/Containers/Connectors) are in the bounds.
     *
     *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
     **/
    getWorkAreaBounds:function() {
        var minX;
        var maxX;
        var minY;
        var maxY;
        var unset = true;   // defines if there were no object - no bounds set

        // function to run for any bounds in format [minX, minY, maxX, maxY]
        // compares given bounds with current values of canvas
        var compareAndSet = function (bounds){
            // if minX is unset or bigger than given
            if (typeof(minX) === 'undefined' || minX > bounds[0]) {
                minX = bounds[0];
            }
            // if minY is unset or bigger than given
            if (typeof(minY) === 'undefined' || minY > bounds[1]) {
                minY = bounds[1];
            }
            // if maxX is unset or bigger than given
            if (typeof(maxX) === 'undefined' || bounds[2] > maxX) {
                maxX = bounds[2];
            }
            // if maxY is unset or bigger than given
            if (typeof(maxY) === 'undefined' || bounds[3] > maxY) {
                maxY = bounds[3];
            }

            // if once function were ran - one object setted it's bounds
            unset = false;
        };

        var i;
        // get bounds of containers
        var containerLength = this.containers.length;
        for (i = 0; i < containerLength; i++) {
            compareAndSet(this.containers[i].getBounds());
        }

        // get bounds of figures
        var figureLength = this.figures.length;
        for (i = 0; i < figureLength; i++) {
            compareAndSet(this.figures[i].getBounds());
        }

        // get bounds of connectors
        var connectorLength = CONNECTOR_MANAGER.connectors.length;
        for (i = 0; i < connectorLength; i++) {
            compareAndSet(CONNECTOR_MANAGER.connectors[i].getBounds());
        }

        // bounds were setted/changed?
        if (unset) {
            // return full canvas size
            return [0, 0, canvasProps.getWidth(), canvasProps.getHeight()];
        } else {
            // return setted new bounds
            return [minX, minY, maxX, maxY];
        }
    },


    /**
     *Apply a transformation to all Figures and Containers in Stack
     *@param {Matrix} matrix - a matrix of numbers
     *
     *@author Artyom Pokatilov <artyom.pokatilov@gmail.com>
     **/
    transform:function(matrix) {
        // translate Containers
        var i;
        var containerLength = this.containers.length;
        for (i = 0; i < containerLength; i++) {
            this.containers[i].transform(matrix);
        }

        // translate Figures
        var figureLength = this.figures.length;
        for (i = 0; i < figureLength; i++) {
            // Does Figure is placed outside of container?
            if (CONTAINER_MANAGER.getContainerForFigure(this.figures[i].id) === -1) {
                this.figures[i].transform(matrix);
            }
            // otherwise it is already transformed
        }
    },


    /**Paints all {Figure}s from back to top (Z order)
     *@param  {Context} context - the 2D context
     *@param  {boolean} ignoreSelection - if ignoreSelection is set to true selections will not be painted
     **/
    paint:function(context, ignoreSelection){
//        Log.group("STACK: paint");
        /*The idea is to paint from bottom to top
         * 1. figures (first)
         * 2. connectors
         * 3. handlers
         * 4. selection area (last) 
         **/
        
        if(DIAGRAMO.debug){
            var  pos = 1;
            context.save();
            context.font = "10px Arial";
            context.fillStyle= '#000000';
            context.strokeStyle= '#000000';
            context.lineWidth = 1;  
            context.fillText("state: " + state, 0, 10 * pos++);
            context.fillText("selectedFigureId: : " + selectedFigureId, 0, 10 * pos++);
            context.fillText("selectedGroupId: : " + selectedGroupId, 0, 10 * pos++);
            context.fillText("selectedContainerId: : " + selectedContainerId, 0, 10 * pos++);
            if(selectedGroupId != -1){
                var logGroup = this.groupGetById(selectedGroupId);
                context.fillText("permanent: : " + logGroup.permanent, 0, 10 * pos++);
            }
            context.fillText("selectedConnectorId: : " + selectedConnectorId, 0, 10 * pos++);
            if(selectedConnectorId != -1){
                var connector = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
                context.fillText("connector type: : " + connector.type, 0, 10 * pos++);
            }
                
            context.restore();
        }
        
        //paint containers
        for(var i=0; i<this.containers.length; i++){
            context.save();
            
            this.containers[i].paint(context);
            
            context.restore();
        }
        //end paint containers
        
        
        //paint figures
        for (var i=0; i<this.figures.length; i++) {

            
            if(!context.save){
                alert("save() no present")
            }
            context.save();

            this.figures[i].paint(context);
            context.restore();

            //if we are connecting something we should paint connection points too
            if(state == STATE_CONNECTOR_PICK_FIRST || state == STATE_CONNECTOR_PICK_SECOND
                || state == STATE_CONNECTOR_MOVE_POINT )
                {
                CONNECTOR_MANAGER.connectionPointPaint(context, this.figures[i].id);
            }
            
        }//end for


        //if we are connecting something we should paint currentCloud too
        if( state == STATE_CONNECTOR_PICK_FIRST || state == STATE_CONNECTOR_PICK_SECOND
            || state == STATE_CONNECTOR_MOVE_POINT )
        {
            CONNECTOR_MANAGER.connectionCloudPaint(context);
        }


        //paint connector(s)
        CONNECTOR_MANAGER.connectorPaint(context, selectedConnectorId);
        
        
        //paint handlers for selected shape
        if(state == STATE_FIGURE_SELECTED){ //FIGURE
            var f = this.figureGetById(selectedFigureId);
            //self dgq
            debugger
            f.style.gradientBounds = f.getBounds();
            HandleManager.shapeSet(f);
            //alert('Paint handles');
			if(!ignoreSelection){
				HandleManager.paint(context);
			}
        }
        else if(state == STATE_CONNECTOR_SELECTED){ //CONNECTOR
            var c = CONNECTOR_MANAGER.connectorGetById(selectedConnectorId);
            HandleManager.shapeSet(c);
			if(!ignoreSelection){
				HandleManager.paint(context);
			}
        }
        else if(state == STATE_CONTAINER_SELECTED){ //CONTAINER
            var cont = STACK.containerGetById(selectedContainerId);
            HandleManager.shapeSet(cont);
            if(!ignoreSelection){
                HandleManager.paint(context);
            }
        }
        else if(state == STATE_GROUP_SELECTED){ //GROUP 
            var g = this.groupGetById(selectedGroupId);
            HandleManager.shapeSet(g);
			if(!ignoreSelection){
				HandleManager.paint(context);
			}
        }
        else if(state == STATE_SELECTING_MULTIPLE){ //SELECTION
            /* If shift is pressed, then leave the selected figure 
             * or group drawn on screen and allow drawing region in same time*/
            if(SHIFT_PRESSED){
                if (selectedFigureId != -1){
                    var f = this.figureGetById(selectedFigureId);
                    HandleManager.paint(context);
                }
                if (selectedGroupId != -1){
                    var g = this.groupGetById(selectedGroupId);
                    HandleManager.paint(context);
                }
            }
            
            selectionArea.paint(context);
            Log.info(selectionArea.toString());
        }
        
        if(DIAGRAMO.debug){
//            Log.group("Visual debug");
            var colors = {
                's0' : '#ff0000',
                's1_1' : '#009900',
                's1_2' : '#0033ff',
                's2_1' : '#00ff33',
                's2_2' : '#ff3399',
                's2_3' : '#808019',
                's2_4' : '#e6e64c',
                's2_5' : '#e67814',
                's2_6' : '#309bda'
            };
            
            context.save();
            for(var i=0; i<DIAGRAMO.debugSolutions.length; i++){
                var shift = 3 + i * 3;
                var solution = DIAGRAMO.debugSolutions[i];
//                Log.info("Solution: " + solution + " type of " + typeof(solution[2]) + " length: " + solution.length );
//                Log.info("Solution points: " + solution[2]);
                var points = solution[2]; //get points
                
                
                //paint line                                
                context.save();
                context.strokeStyle = colors[solution[1]];
                context.lineWidth = 1;  
                context.beginPath();
                context.moveTo(points[0].x + shift, points[0].y + shift);                
                for(var j=1; j<points.length; j++){                                        
                    context.lineTo(points[j].x + shift, points[j].y + shift)
                }
                //context.closePath();
                context.stroke();
                context.restore();                    


                //paint points
                for(var j=0; j<points.length; j++){
                    context.save();
//                    points[j].style.strokeStyle = '#FF0000';
//                    points[j].paint(context);
                    context.beginPath();
                    //context.strokeStyle= colors[solution[1]];
                    context.strokeStyle= '#cccccc';
                    //context.arc(points[j].x,points[j].y, 2 + i * 3, 0, Math.PI*2, true);
                    context.arc(points[j].x,points[j].y, 3, 0, Math.PI*2, true);
                    //context.closePath();
                    context.stroke();
                    context.restore();                    
                }                                
            }
            
            //paint the legend
            pos += 4;
            for(var solName in colors){
                context.save();
                context.strokeText(solName, 0, 10 * pos);
                context.strokeStyle = colors[solName];
                context.beginPath();
                context.moveTo(50, 10 * pos);
                context.lineTo(150, 10 * pos);
                //context.endPath();
                context.stroke();
                context.restore();
                pos += 2;
            }
            context.restore();
            
//            Log.groupEnd();
        }
        
//        Log.groupEnd();
    },

    /**Convert all STACK to SVG representation
     *@return {String} - the SVG string representation*/
    toSVG : function(){
        var svg = '';

        for (var i=0; i<this.figures.length; i++) {
            svg += this.figures[i].toSVG();
        }
        
        return svg;
    }
}